package logger

import (
	"os"
	"path/filepath"
	"time"

	"github.com/natefinch/lumberjack"
	"github.com/pkg/errors"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

type zapLogger struct {
	infoLogger  *zap.SugaredLogger
	errorLogger *zap.SugaredLogger
	SqlLogger   *zap.SugaredLogger
}

// NewZapLogger 获取zap日志记录实体
func NewZapLogger(logDirectory string) (Logger, error) {
	// Create info log directory
	infoLogDirectory := filepath.Join(logDirectory, "info")
	if err := os.MkdirAll(infoLogDirectory, os.ModePerm); err != nil {
		return nil, errors.Wrap(err, "failed to create info log directory")
	}

	// Create error log directory
	errorLogDirectory := filepath.Join(logDirectory, "error")
	if err := os.MkdirAll(errorLogDirectory, os.ModePerm); err != nil {
		return nil, errors.Wrap(err, "failed to create error log directory")
	}

	sqlLogDirectory := filepath.Join(logDirectory, "sql")
	if err := os.MkdirAll(sqlLogDirectory, os.ModePerm); err != nil {
		return nil, errors.Wrap(err, "failed to create sql log directory")
	}

	// Create lumberjack logger for info logs
	infoLogger := &lumberjack.Logger{
		Filename:   filepath.Join(infoLogDirectory, "info.log"),
		MaxSize:    100, // MB
		MaxBackups: 10,
		MaxAge:     30, // Days
		LocalTime:  true,
	}

	// Create lumberjack logger for error logs
	errorLogger := &lumberjack.Logger{
		Filename:   filepath.Join(errorLogDirectory, "error.log"),
		MaxBackups: 10,
		MaxAge:     30,   // Days
		LocalTime:  true, // Use local time for file names
	}

	sqlLogger := &lumberjack.Logger{
		Filename:   filepath.Join(sqlLogDirectory, "sql.log"),
		MaxBackups: 10,
		MaxAge:     30,   // Days
		LocalTime:  true, // Use local time for file names
	}

	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = timeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.LineEnding = zapcore.DefaultLineEnding
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder

	// Create zap cores
	infoCore := zapcore.NewCore(
		zapcore.NewConsoleEncoder(encoderConfig),
		zapcore.AddSync(infoLogger),
		zapcore.InfoLevel, // Default log level is Info
	)

	errorCore := zapcore.NewCore(
		zapcore.NewConsoleEncoder(encoderConfig),
		zapcore.AddSync(errorLogger),
		zapcore.ErrorLevel, // Default log level is Error
	)

	sqlCore := zapcore.NewCore(
		zapcore.NewConsoleEncoder(encoderConfig),
		zapcore.AddSync(sqlLogger),
		zapcore.InfoLevel, // Default log level is Error
	)

	// Create zap loggers
	infoZapLogger := zap.New(infoCore).Sugar()
	errorZapLogger := zap.New(errorCore).Sugar()
	sqlZapLogger := zap.New(sqlCore).Sugar()

	// Schedule log rotation at midnight
	go rotateLogsAtMidnight(infoLogger, errorLogger, sqlLogger)

	return &zapLogger{
		infoLogger:  infoZapLogger,
		errorLogger: errorZapLogger,
		SqlLogger:   sqlZapLogger,
	}, nil
}

func (l *zapLogger) Info(args ...interface{}) {
	l.infoLogger.Info("\n", args, "\n")
}

func (l *zapLogger) Error(args ...interface{}) {
	l.errorLogger.Error("\n", args)
}

func (l *zapLogger) Sql(args ...interface{}) {
	l.SqlLogger.Info(args)
}

func rotateLogsAtMidnight(infoLogger, errorLogger *lumberjack.Logger, sqlZapLogger *lumberjack.Logger) {
	for {
		now := time.Now()
		next := now.Add(time.Hour * 24)
		next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
		t := time.NewTimer(next.Sub(now))
		<-t.C

		// Rotate logs
		_ = infoLogger.Rotate()
		_ = errorLogger.Rotate()
		_ = sqlZapLogger.Rotate()
	}
}

func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
